package com.boot.project.module.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.project.core.constant.RedisConstant;
import com.boot.project.core.constant.SystemConstant;
import com.boot.project.core.enums.code.impl.commonCode.ResultCode;
import com.boot.project.core.enums.code.impl.systemCode.UserCode;
import com.boot.project.core.exception.CustomerException;
import com.boot.project.core.model.result.PermissionResult;
import com.boot.project.core.model.result.ProfileResult;
import com.boot.project.core.model.system.criteria.UserCriteria;
import com.boot.project.core.model.system.dto.UserAssignDto;
import com.boot.project.core.model.system.dto.UserLoginDto;
import com.boot.project.core.model.system.dto.UserPasswordDto;
import com.boot.project.core.model.system.dto.UserSaveDto;
import com.boot.project.core.model.system.entity.Resource;
import com.boot.project.core.model.system.entity.Role;
import com.boot.project.core.model.system.entity.User;
import com.boot.project.core.model.system.vo.UserVo;
import com.boot.project.core.util.RequestUtils;
import com.boot.project.module.system.mapper.ResourceMapper;
import com.boot.project.module.system.mapper.RoleMapper;
import com.boot.project.module.system.mapper.UserMapper;
import com.boot.project.module.system.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: tsing
 * @create: 2021-01-21 22:21
 */
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private ResourceMapper resourceMapper;

    @Autowired
    private BCryptPasswordEncoder encoder;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 分页条件查询
     * @param criteria 查询条件
     * @return
     */
    @Override
    public Page<UserVo> page(UserCriteria criteria) {
        Page<UserVo> pageInfo = new Page<>(criteria.getPage(), criteria.getSize());
        userMapper.pageList(pageInfo, criteria);
        if (pageInfo.getTotal() == 0) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        return pageInfo;
    }

    /**
     * 新增用户信息
     *
     * @param userSaveDto 用户信息
     */
    @Override
    public void add(UserSaveDto userSaveDto) {
        // 1.校验参数
        User dbUser = userMapper.findByUsername(userSaveDto.getUsername());
        if (dbUser != null) {
            throw new CustomerException(UserCode.EXISTS_USERNAME);
        }
        dbUser = userMapper.findByMobile(userSaveDto.getMobile());
        if (dbUser != null) {
            throw new CustomerException(UserCode.EXISTS_MOBILE);
        }
        // 2.封装数据
        User user = new User(userSaveDto);
        user.setPassword(encoder.encode(SystemConstant.DEFAULT_PASSWORD));
        // 3.进行保存
        int i = userMapper.insert(user);
        // 4.对结果进行处理
        if (i != 1) {
            throw new CustomerException(ResultCode.INSERT_FAILURE);
        }
    }

    /**
     * 根据id查询用户信息
     *
     * @param id 用户id
     * @return
     */
    @Override
    public User findOne(Long id) {
        // 1.进行查询
        User dbUser = userMapper.selectById(id);
        // 2.对结果进行判断
        if (dbUser == null) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND);
        }
        return dbUser;
    }

    /**
     * 更新用户信息
     *
     * @param userSaveDto 用户信息
     */
    @Override
    public void edit(UserSaveDto userSaveDto) {
        // 1.校验参数
        if (userSaveDto.getId() == null) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "更新时，用户id不能为空");
        }
        User dbUser = userMapper.selectById(userSaveDto.getId());
        if (dbUser == null) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "更新时，没有找到需要更新的用户");
        }
        User tempUser = userMapper.findByUsername(userSaveDto.getUsername());
        if (tempUser != null && !tempUser.getId().equals(userSaveDto.getId())) {
            throw new CustomerException(UserCode.EXISTS_USERNAME);
        }
        tempUser = userMapper.findByMobile(userSaveDto.getMobile());
        if (tempUser != null && !tempUser.getId().equals(userSaveDto.getId())) {
            throw new CustomerException(UserCode.EXISTS_MOBILE);
        }
        // 2.封装数据
        BeanUtils.copyProperties(userSaveDto, dbUser);
        // 3.进行保存
        int i = userMapper.updateById(dbUser);
        // 4.对结果进行处理
        if (i != 1) {
            throw new CustomerException(ResultCode.UPDATE_FAILURE);
        }
    }

    /**
     * 批量删除用户信息
     *
     * @param ids 用户id的集合
     */
    @Override
    public void delete(List<Long> ids) {
        // 1.参数校验
        if (CollectionUtil.isEmpty(ids)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "没有需要删除的用户信息");
        }
        // 2.进行删除
        int i = userMapper.deleteBatchIds(ids);
        // 3.对结果进行处理
        if (i != ids.size()) {
            throw new CustomerException(ResultCode.DELETE_FAILURE);
        }
    }

    /**
     * 用户登录
     *
     * @param dto 登录信息
     * @return
     */
    @Override
    public Map<String, Object> login(UserLoginDto dto) {
        User dbUser = userMapper.findByUsername(dto.getUsername());
        if (dbUser == null) {
            throw new CustomerException(UserCode.INCORRECT_USERNAME_OR_PASSWORD);
        }
        boolean matches = encoder.matches(dto.getPassword(), dbUser.getPassword());
        if (!matches) {
            throw new CustomerException(UserCode.INCORRECT_USERNAME_OR_PASSWORD);
        }
        UserVo vo = new UserVo(dbUser);
        Role role = roleMapper.selectById(dbUser.getRoleId());
        if (role != null) {
            vo.setRoleName(role.getRoleName());
        }
        // 生成uuid
        String uuid = UUID.randomUUID().toString();
        // 将其和roleId的对应关系放到redis里
        BoundValueOperations operations = redisTemplate.boundValueOps(RedisConstant.REDIS_LOGIN_USER_PREFIX + uuid);
        operations.set(vo, 30, TimeUnit.MINUTES);
        Map<String, Object> map = new HashMap<>();
        map.put("token", uuid);
        return map;
    }

    /**
     * 根据用户的标识查找对应的权限信息
     *
     * @return
     */
    @Override
    public ProfileResult profile() {
        String token = ServletUtil.getCookie(RequestUtils.getRequest(), "token").getValue();
        // 1.从redis中根据uuid拿到user信息
        String userRedisKey = RedisConstant.REDIS_LOGIN_USER_PREFIX + token;
        BoundValueOperations operations = redisTemplate.boundValueOps(userRedisKey);
        if (!redisTemplate.hasKey(userRedisKey)) {
            throw new CustomerException(UserCode.NOT_LOG_IN);
        }
        // 获取用户信息
        UserVo redisUser = (UserVo) operations.get();

        // 2.从redis中根据roleId去拿权限信息
        String roleRedisKey = RedisConstant.REDIS_ROLE_PROFILE_PREFIX + redisUser.getRoleId();
        PermissionResult result;
        operations = redisTemplate.boundValueOps(roleRedisKey);
        if (redisTemplate.hasKey(roleRedisKey)) {
            result = (PermissionResult) operations.get();
        } else {
            result = buildProfile(redisUser);
            operations.set(result);
        }
        return new ProfileResult(redisUser.getId(), redisUser.getUsername(), redisUser.getRoleId(),
                redisUser.getRoleName(), result);
    }

    /**
     * 管理员重置用户密码，将密码重置为初始密码
     *
     * @param userPasswordDto 操作需要的实体信息
     */
    @Override
    public void resetPassword(UserPasswordDto userPasswordDto) {
        if (ObjectUtil.isNull(userPasswordDto.getId())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "重置用户密码时，没有用户的id");
        }
        User dbUser = userMapper.selectById(userPasswordDto.getId());
        if (ObjectUtil.isNull(dbUser)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "重置用户密码时，没有用户的信息");
        }
        dbUser.setPassword(encoder.encode(SystemConstant.DEFAULT_PASSWORD));
        int i = userMapper.updateById(dbUser);
        if (i != 1) {
            throw new CustomerException(UserCode.PASSWORD_RESET_FAILURE);
        }
    }

    /**
     * 用户修改自己的密码
     *
     * @param userPasswordDto 操作需要的实体信息
     */
    @Override
    public void editPassword(UserPasswordDto userPasswordDto) {
        if (ObjectUtil.isNull(userPasswordDto.getId())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，没有用户的id");
        }
        if (StrUtil.isBlank(userPasswordDto.getOldPassword())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，原密码不能为空");
        }
        if (StrUtil.isBlank(userPasswordDto.getNewPassword())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，新密码不能为空");
        }
        User dbUser = userMapper.selectById(userPasswordDto.getId());
        if (ObjectUtil.isNull(dbUser)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，没有用户的信息");
        }
        boolean matches = encoder.matches(userPasswordDto.getOldPassword(), dbUser.getPassword());
        if (!matches) {
            throw new CustomerException(UserCode.INCORRECT_OLD_PASSWORD);
        }
        dbUser.setPassword(encoder.encode(userPasswordDto.getNewPassword()));
        int i = userMapper.updateById(dbUser);
        if (i != 1) {
            throw new CustomerException(UserCode.PASSWORD_EDIT_FAILURE);
        }
    }

    /**
     * 当用户密码为初始密码时，强制修改用户密码
     *
     * @param userPasswordDto 操作需要的实体信息
     */
    @Override
    public void forceEditPassword(UserPasswordDto userPasswordDto) {
        if (ObjectUtil.isNull(userPasswordDto.getId())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，没有该用户的id");
        }
        if (StrUtil.isBlank(userPasswordDto.getNewPassword())) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，新密码不能为空");
        }
        User dbUser = userMapper.selectById(userPasswordDto.getId());
        if (ObjectUtil.isNull(dbUser)) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "修改用户密码时，没有该用户的信息");
        }
        dbUser.setPassword(encoder.encode(userPasswordDto.getNewPassword()));
        int i = userMapper.updateById(dbUser);
        if (i != 1) {
            throw new CustomerException(UserCode.PASSWORD_FORCE_EDIT_FAILURE);
        }
    }

    /**
     * 设置用户角色
     *
     * @param dto 设置的信息
     */
    @Override
    public void assignRole(UserAssignDto dto) {
        // 1.校验参数
        if (dto.getUserId() == null) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "分配角色时，用户id不能为空");
        }
        User dbUser = userMapper.selectById(dto.getUserId());
        if (dbUser == null) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "分配角色时，没有找到对应的用户信息");
        }
        if (dto.getRoleId() == null) {
            throw new CustomerException(ResultCode.HANDLE_DATA_NOT_EXISTED, "分配角色时，角色id不能为空");
        }
        Role dbRole = roleMapper.selectById(dto.getRoleId());
        if (dbRole == null) {
            throw new CustomerException(ResultCode.RETURN_DATA_NOT_FOUND, "分配角色时，没有找到对应的角色信息");
        }
        // 2.封装数据
        dbUser.setRoleId(dto.getRoleId());
        // 3.进行保存
        int i = userMapper.updateById(dbUser);
        // 4.对结果进行处理
        if (i != 1) {
            throw new CustomerException(UserCode.ASSIGN_ROLE_FAILURE);
        }
        // 5.删除对应的缓存
        String token = ServletUtil.getCookie(RequestUtils.getRequest(), "token").getValue();
        if (StrUtil.isNotBlank(token)) {
            redisTemplate.delete(RedisConstant.REDIS_LOGIN_USER_PREFIX + "token");
        }
    }

    /**
     * 用户登出
     */
    @Override
    public void logout() {
        String token = ServletUtil.getCookie(RequestUtils.getRequest(), "token").getValue();
        redisTemplate.delete(RedisConstant.REDIS_LOGIN_USER_PREFIX + token);
    }

    /**
     * 根据用户信息构建权限信息
     * @param vo 用户信息
     * @return
     */
    private PermissionResult buildProfile(UserVo vo) {
        List<Resource> menus = new ArrayList<>();
        Set<String> buttons = new LinkedHashSet<>();
        // 1.根据roleId查询该角色所拥有的全部权限
        List<Resource> list = resourceMapper.findByRoleId(vo.getRoleId());
        if (CollectionUtils.isEmpty(list)) {
            return new PermissionResult();
        }
        // 2.将list中的数据根据type的不同封装到不同的集合中去
        for (Resource resource : list) {
            if (SystemConstant.RESOURCE_TYPE_MENU.equals(resource.getType())) {
                menus.add(resource);
            }
            if (StrUtil.isNotBlank(resource.getPermission())) {
                String[] pArray = resource.getPermission().split(",");
                Collections.addAll(buttons, pArray);
            }
        }
        // 3.将menus转成有父子关系的对象List
        List<Map<String, Object>> treeMenus = new ArrayList<>();
        if (!CollectionUtils.isEmpty(menus)) {
            treeMenus = transListToTreeList(menus);
        }
        return new PermissionResult(treeMenus, buttons);
    }

    /**
     * 将menus转成有父子关系的对象List，关联属性为parentId。只适用于二级菜单
     * @param menus 需要转化的集合
     * @return
     */
    private List<Map<String, Object>> transListToTreeList(List<Resource> menus) {
        // 先根据parentId排序
        Collections.sort(menus, Comparator.comparingInt(o -> o.getParentId().intValue()));

        // 存放树形的处理结果的list
        List<Map<String, Object>> treeList = new ArrayList<>();
        // 这个map用于存放returnList中元素的id和其索引的对应关系，即key是menu的id值，值是这个menu在returnList中的索引
        Map<Long, Integer> indexMap = new HashMap<>();
        for (Resource resource: menus) {
            if (resource.getParentId() == 0) {
                // 父菜单的情况
                Map<String, Object> subMap = new HashMap<>();
                subMap.put("id", resource.getId());
                subMap.put("icon", resource.getIcon());
                subMap.put("name", resource.getName());
                subMap.put("url", resource.getUrl());
                subMap.put("sort", resource.getSort());
                subMap.put("children", new ArrayList<Map<String, Object>>());
                treeList.add(subMap);
                indexMap.put(resource.getId(), treeList.size() - 1);
            } else {
                // 子菜单的情况
                List<Map<String, Object>> children = (List<Map<String, Object>>) treeList
                        .get(indexMap.get(resource.getParentId())).get("children");
                Map<String, Object> subMap = new HashMap<>();
                subMap.put("id", resource.getId());
                subMap.put("name", resource.getName());
                subMap.put("url", resource.getUrl());
                subMap.put("sort", resource.getSort());
                children.add(subMap);
            }
        }

        // 排序
        // 1.先排子菜单
        for (Map<String, Object> map : treeList) {
            if (map.get("children") != null) {
                Collections.sort((List<Map<String, Object>>) map.get("children"), (o1, o2) -> {
                    Integer sort1 = (Integer) o1.get("sort");
                    Integer sort2 = (Integer) o2.get("sort");
                    return sort1.compareTo(sort2);

                });
            }
        }

        // 2.再排父菜单
        Collections.sort(treeList, (o1, o2) -> {
            Integer sort1 = (Integer) o1.get("sort");
            Integer sort2 = (Integer) o2.get("sort");
            return sort1.compareTo(sort2);
        });
        return treeList;
    }
}
